#include "Server.h"
#include "MapManager.h"
#include "drogon/drogon.h"
#include "OCMain/FileUtil.h"
#include "OCMain/StringUtil.h"
#include "OCMain/Environment.h"
#include "OCMain/Log.h"
#include "OCMain/DynLib.h"
#include "OCMain/DynLibManager.h"

typedef void (*DLL_START_PLUGIN)(void);
typedef void (*DLL_STOP_PLUGIN)(void);

namespace OCPP
{
	Server::Server()
		:mMapManager(NULL)
	{

	}

	Server::~Server()
	{
		drogon::app().quit();
		unloadPlugins();
		CORE_SAFE_DELETE(mMapManager);
	}

	String Server::generateHomeService()
	{
		String appStartTime = StringUtil::getTime();
		StringStream sstream;
		sstream << "<p>Server start at " << appStartTime << "</p>";
		for (auto& lib : mPluginLibs)
		{
			String name, path, ext;
			StringUtil::splitFullFilename(lib->getName(), name, path, ext);
			name = name.substr(15, name.length());
#ifdef _DEBUG
			name = name.substr(0, name.length() - 1);
#endif // _DEBUG
			sstream << "<br><a href=\"/" << name << "/doc\">" << name << "</a>";
		}
		return sstream.str();
	}

	void Server::loadPlugins()
	{
		mMapManager = new MapManager();

		String appPath = Singleton(Environment).getAppPath();
		StringVector ymlFiles = FileUtil::findFiles(joinPath(appPath, "config"));

		for (const auto& fileName : ymlFiles)
		{
			if (fileName == "." || fileName == "..")
			{
				continue;
			}
			String name, ext;
			StringUtil::splitBaseFilename(fileName, name, ext);

			if (ext != "yml")
			{
				continue;
			}
#ifndef WIN32
			name = "lib" + name;
#endif // !WIN32

			String libFileName = joinPath(appPath, name);
#if _DEBUG
			libFileName += "d";
#endif
			DLL_START_PLUGIN startFunc = nullptr;

			StringStream logMessage;
			logMessage << "Load plugin:" << libFileName;
			if (DynLib* lib = Singleton(DynLibManager).load(libFileName))
			{
				if (DLL_START_PLUGIN startFunc = (DLL_START_PLUGIN)lib->getSymbol("dllStartPlugin"))
				{
					LOG_INFO << logMessage.str() << "\n";
					startFunc();
				}
				else
				{
					LOG_ERROR << logMessage.str() << " fail.\n";
				}
				mPluginLibs.push_back(lib);
			}
		}
	}

	void Server::unloadPlugins()
	{
		for (auto lib : mPluginLibs)
		{
			if (DLL_STOP_PLUGIN pFunc = (DLL_STOP_PLUGIN)lib->getSymbol("dllStopPlugin"))
			{
				pFunc();
			}
		}
	}

	void Server::start(int port)
	{
		String appPath = Singleton(Environment).getAppPath();
		LOG_INFO << "Workspace:" << appPath << "\n";
		String configFileName = appPath + "config.json";
		drogon::app().loadConfigFile(A2U8(configFileName));


#if _DEBUG
		drogon::app().setLogLevel(trantor::Logger::kDebug);
#endif
		//initial
		loadPlugins();
		String homeBody = generateHomeService();

		drogon::app().registerHandler(
			"/",
			[&homeBody](const drogon::HttpRequestPtr&,
				std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
					auto resp = drogon::HttpResponse::newHttpResponse();
					resp->setBody(homeBody);
					callback(resp);
			},
			{ drogon::Get });

		//inject log
		auto logLevelToString = [](Log::LogLevel logLevel)
			{
				if (logLevel == Log::LogLevel::kDebug)
					return String("debug");
				if (logLevel == Log::LogLevel::kInfo)
					return String("info");
				if (logLevel == Log::LogLevel::kError)
					return String("error");
				return std::to_string((int)logLevel);
			};
		drogon::app().getLoop()->runAfter(0, [this, &logLevelToString]() {
			Log::LogLevel logLevel = (Log::LogLevel)trantor::Logger::logLevel();
			LOG_INFO << "LogLevel:" << logLevelToString(logLevel) << "\n";
			Log::logLevel() = logLevel;
			Log::setOutputFunction([](const char* msg, const uint64_t len) {
				trantor::RawLogger().stream() << String(msg, len);
				}, []() {});
			});

		LOG_INFO << "Server running on port:" << port << "\n";
		LOG_INFO << "Local http://localhost:" << port << "\n";

		drogon::app().addListener("0.0.0.0", port);
		drogon::app().run();
	}
}
